package com.dux.commons.scripts.java;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandlerFactory;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.log4j.Logger;

import com.dux.commons.scripts.java.url.VirtualClassURLStreamHandler;


/**
 * 脚本加载器
 * @author 杜祥
 * @create 2013-5-11
 */
public abstract class ScriptClassLoader extends URLClassLoader 
{
	
	private static final Logger log = Logger.getLogger(ScriptClassLoader.class);
	
	
	/**
	 * URL流处理类
	 */
	private final VirtualClassURLStreamHandler urlStreamHandler = new VirtualClassURLStreamHandler(this);
	
	
	/**
	 * 需要加载的类库
	 */
	protected Set<String> libraryClasses = new HashSet<String>();

	
	
	public ScriptClassLoader(URL[] urls, ClassLoader parent) 
	{
		super(urls, parent);
	}
	
	
	public ScriptClassLoader(URL[] urls)
	{
		super(urls);
	}
	
	
	public ScriptClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory)
	{
		super(urls, parent, factory);
	}

	/**
	 * 加载指定路径下的jar包里的class文件
	 * 
	 * @param file
	 *            jar file
	 * @throws IOException 
	 * @throws IOException
	 *             if can't add library
	 */
	public void addLibrary(File file) throws IOException
	{
		/*通过指定的路径获得该路径的URL*/
		URL fileURL = file.toURI().toURL();
		
		/*将指定的 URL 添加到 URL 列表中*/
		super.addURL(fileURL);
		
		JarFile jarFile = new JarFile(file);
		
		Enumeration<JarEntry> entries = jarFile.entries();
		/*遍历jar下面的所有项*/
		while(entries.hasMoreElements())
		{
			JarEntry entry = entries.nextElement();
			
			String name = entry.getName();
			/*判断后缀名是否为.class*/
			if(name.endsWith(".class"))
			{
				name = name.substring(0, name.length() - 6);
				
				name = name.replace('/', '.');
				/*加入进类库*/
				libraryClasses.add(name);
			}
		}
		jarFile.close();
	}
	
	
	/**
	 * 查找具有给定名称的资源。
	 * 
	 * @param name
	 * 					类名
	 */
	@Override
	public URL getResource(String name)
	{
		if(!name.endsWith(".class"))
		{
			return super.getResource(name);
		}
		else
		{
			String newName = name.substring(0, name.length() - 6);
			
			newName = newName.replace('/', '.');
			
			if(getCompiledClasses().contains(newName))
			{
				try
				{
					return new URL(null, VirtualClassURLStreamHandler.HANDLER_PROTOCOL + newName, urlStreamHandler);
				} 
				catch (MalformedURLException e)
				{
					StringBuilder builder = new StringBuilder("URL[").append(newName).append("]指向的类无法编译创建URL。");
					log.error(builder.toString(), e);
				}
			}
		}
		
		return super.getResource(name);
	}
	
	
	/**
	 * 从库中加载类
	 * @param name
	 * 				需要加载的类名
	 * @return 
	 * 				返回加载的类
	 */
	public Class<?> loadClass(String name) throws ClassNotFoundException
	{
		boolean isCompiled = getCompiledClasses().contains(name);
		
		/*编译完成的所有类是否有此类*/
		if(!isCompiled)
		{
			return super.loadClass(name, true);
		}
		
		Class<?> c = getDefinedClass(name);
		
		if(c == null)
		{
			byte[] b = getByteCode(name);
			
			c = super.defineClass(name, b, 0, b.length);
			
			setDefinedClass(name, c);
		}
		
		return c;
	}
	
	
	/**
	 * 返回不可修改的类库
	 * @return
	 * 			不可修改的类库
	 */
	public Set<String> getLibraryClasses()
	{
		return Collections.unmodifiableSet(libraryClasses);
	}
	
	
	/**
	 * 返回不可修改的已编译完成的类名
	 * @return
	 * 			不可修改已编译类名列表
	 */
	public abstract Set<String> getCompiledClasses();
	
	
	/**
	 * 返回指定类名的字节码
	 * 返回byte[]是字节码的副本，这样修改将不会受到伤害。
	 * @param className
	 * 					类名
	 * @return
	 * 					字节码
	 */
	public abstract byte[] getByteCode(String className);
	
	
	/**
	 * 返回缓存的类的实例名称，如果未缓存则返回空
	 * @param name
	 * 					类的名称
	 * @return
	 * 				缓存的类实例或则null
	 */
	public abstract Class<?> getDefinedClass(String name);
	
	
	/**
	 * 将指定的类设置到缓存中
	 * @param name
	 * 					类名
	 * @param clazz
	 * 					实例
	 * @throws IllegalArgumentException
	 * 					
	 */
	public abstract void setDefinedClass(String name, Class<?> clazz) throws IllegalArgumentException;
}
